package uk.co.droidinactu.inlibrislibertas.model;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import uk.co.droidinactu.common.model.AbstractDataModel;
import uk.co.droidinactu.common.model.AbstractDataObj;
import uk.co.droidinactu.inlibrislibertas.LibraryConstants;
import uk.co.droidinactu.inlibrislibertas.contentprovider.InLibrisLibertasData;
import uk.co.droidinactu.inlibrislibertas.db.DatabaseController;
import uk.co.droidinactu.inlibrislibertas.db.DatabaseHelperFactory;
import uk.co.droidinactu.inlibrislibertas.db.DbConstants;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.provider.BaseColumns;
import android.util.Log;

/**
 * this class is the base class for all my data models. this is used so that an
 * observer can be informed of a change to the model rather than of a change to
 * an individual data item.
 * 
 * @author andy
 */
public class CalibreDataModel extends AbstractDataModel {

	private static final String LOG_TAG = "InLibris_DataModel:";

	private Cursor authorsCursor;
	private Context contxt = null;
	private final DatabaseController dbCntrlr;
	private DbMetadata dbMetaDta = new DbMetadata();

	private Cursor ebooksCursor = null;
	private String filterString = "";
	private Handler progressHandler;

	public CalibreDataModel(final Context cntxt) {
		contxt = cntxt;
		dbCntrlr = new DatabaseController();
		dbCntrlr.initialise(cntxt, new DbConstants(), new DatabaseHelperFactory());
		getDbMetadata();
	}

	public Long addAuthor(final String anAuthor) {
		Author tmpAuth = new Author(anAuthor);
		final Cursor results = dbCntrlr.selectData(tmpAuth.getSelectAuthor());

		if (results.getCount() == 0) {
			try {
				final Date now = new Date();
				tmpAuth.setLastUpdated(now.getTime());

				contxt.getContentResolver().insert(InLibrisLibertasData.Authors.CONTENT_URI,
						tmpAuth.getContentValues());

				tmpAuth = this.getAuthor(tmpAuth);
			} catch (final SQLException e) {
				Log.e(LibraryConstants.LOG_TAG + CalibreDataModel.LOG_TAG,
						"Error inserting new author : " + e.toString(), e);
			}
		}
		return tmpAuth.getUniqueIdentifier();
	}

	public Integer addAuthorBookLink(final Author anAuthor, final EBook ebk) {
		final AuthorEbookLink tmpAuth = new AuthorEbookLink(anAuthor.getUniqueIdentifier(),
				ebk.getUniqueIdentifier());
		final Date now = new Date();
		tmpAuth.setLastUpdated(now.getTime());
		dbCntrlr.insert(new AuthorEbookLink().getTableName(), null, tmpAuth.getContentValues());
		dbCntrlr.commit();
		return null;
	}

	public Integer addAuthorBookLink(final String authorName, final EBook ebk) {
		Author anAuthor = this.getAuthor(authorName);
		if (anAuthor == null) {
			final Long authId = addAuthor(authorName);
			anAuthor = this.getAuthor(authId);
		}
		return this.addAuthorBookLink(anAuthor, ebk);
	}

	public void addAuthorViaHandler(final String authorStr) {
		if (progressHandler != null) {
			final Message hndlrMsg = progressHandler.obtainMessage();
			hndlrMsg.obj = new Author(authorStr);
			progressHandler.sendMessage(hndlrMsg);
		}
	}

	public Long addBook(EBook eBk) {
		try {
			eBk.setLastUpdated(new Date().getTime());

			contxt.getContentResolver().insert(InLibrisLibertasData.EBooks.CONTENT_URI,
					eBk.getContentValues());
			eBk = getBook(eBk);

		} catch (final SQLException e) {
			Log.e(LibraryConstants.LOG_TAG + CalibreDataModel.LOG_TAG,
					"Error inserting new author : " + e.toString(), e);
		}
		return eBk.getUniqueIdentifier();
	}

	public void addRecentBook(final EBook bookDetails) {
		final RecentEBook rbk = new RecentEBook();
		rbk.ebookId = bookDetails.getUniqueIdentifier();
		rbk.setLastUpdated(new Date().getTime());
		dbCntrlr.insert(rbk.getTableName(), null, rbk.getContentValues());
	}

	public Long addTag(final String aTag) {
		final Tag tmpTag = new Tag(aTag);
		final Cursor results = dbCntrlr.selectData(tmpTag.getSelectTag());

		if (results.getCount() == 0) {
			try {
				final Date now = new Date();
				tmpTag.setLastUpdated(now.getTime());
				dbCntrlr.insert(tmpTag.getTableName(), null, tmpTag.getContentValues());
				dbCntrlr.commit();
			} catch (final SQLException e) {
				Log.e(LibraryConstants.LOG_TAG + CalibreDataModel.LOG_TAG,
						"Error inserting new author : " + e.toString(), e);
			}
		}
		return null;
	}

	public Integer addTagBookLink(final String tagName, final EBook ebk) {
		Tag aTag = this.getTag(tagName);
		if (aTag == null) {
			addTag(tagName);
			aTag = this.getTag(tagName);
		}
		return this.addTagBookLink(aTag, ebk);
	}

	public Integer addTagBookLink(final Tag aTag, final EBook ebk) {
		final TagEbookLink tmpTgBk = new TagEbookLink(aTag.getUniqueIdentifier(), ebk.getUniqueIdentifier());
		final Date now = new Date();
		tmpTgBk.setLastUpdated(now.getTime());
		dbCntrlr.insert(new TagEbookLink().getTableName(), null, tmpTgBk.getContentValues());
		dbCntrlr.commit();
		return null;
	}

	public void clearDb() {
		dbCntrlr.executeSql(new EBook().getSqlDeleteAll());
		dbCntrlr.executeSql(new Author().getSqlDeleteAll());
		dbCntrlr.executeSql(new AuthorEbookLink().getSqlDeleteAll());
		dbCntrlr.executeSql(new Tag().getSqlDeleteAll());
		dbCntrlr.executeSql(new TagEbookLink().getSqlDeleteAll());
		dbCntrlr.executeSql(new RecentEBook().getSqlDeleteAll());
	}

	private void createViews() {
		final String sql = "CREATE VIEW BookDetails if not exists as select * from "
				+ new EBook().getTableName() + "";

	}

	public void deleteBook(final EBook selectedBook) {
		dbCntrlr.delete(selectedBook);
		final File f = new File(File.separatorChar + "sdcard" + java.io.File.separatorChar + selectedBook);
		try {
			f.delete();
			f.deleteOnExit();
		} catch (final Exception e) {
		}
		setChanged();
		try {
			this.notifyObservers(new EBook());
		} catch (final Exception e) {
			Log.e(LibraryConstants.LOG_TAG + CalibreDataModel.LOG_TAG,
					"deleteBook(): exception notifying observers: " + e.getMessage(), e);
		}
	}

	private ArrayList<EBook> filterBookList(final ArrayList<EBook> listToFilter) {
		if (filterString == "") {
			return listToFilter;
		}

		final ArrayList<FilterEntry> filterTokns = parseFilter(filterString);
		ArrayList<EBook> bkListTmp = new ArrayList<EBook>();

		if (filterTokns.size() < 1) {
			bkListTmp = listToFilter;
		} else {

			final ArrayList<EBook> bkLst = listToFilter;

			for (int x = 0; x < bkLst.size(); x++) {
				final EBook cb = bkLst.get(x);
				// Go through the filters to see if this book matches
				boolean orMatch = false;
				boolean andMatch = true;

				if (filterTokns.size() == 1) {
					final FilterEntry fe = filterTokns.get(0);
					final boolean bkMatchsTkn = cb.matchesFilter(fe);

					if (fe.boolOp == BooleanOperator.ANDNOT || fe.boolOp == BooleanOperator.ORNOT) {
						if (bkMatchsTkn) {
							orMatch = false;
							andMatch = false;
						}
						orMatch = true;
					} else if (fe.boolOp == BooleanOperator.AND) {
						if (!bkMatchsTkn) {
							andMatch = false;
						}
					} else if (fe.boolOp == BooleanOperator.OR) {
						if (bkMatchsTkn) {
							orMatch = true;
						}
					}
					if (andMatch && orMatch) {
						bkListTmp.add(cb);
					}

				} else {

					for (final FilterEntry fe : filterTokns) {
						final boolean bkMatchsTkn = cb.matchesFilter(fe);

						if (fe.boolOp == BooleanOperator.ANDNOT) {
							if (bkMatchsTkn) {
								orMatch = false;
								andMatch = false;
								break;
							}
						} else if (fe.boolOp == BooleanOperator.ORNOT) {
							if (bkMatchsTkn) {
								orMatch = false;
								andMatch = false;
								break;
							}
							orMatch = true;
						} else if (fe.boolOp == BooleanOperator.AND) {
							if (!bkMatchsTkn) {
								andMatch = false;
							}
						} else if (fe.boolOp == BooleanOperator.OR) {
							if (bkMatchsTkn) {
								orMatch = true;
							}
						}
					}
					if (andMatch && orMatch) {
						bkListTmp.add(cb);
					}
				}
			}
		}
		return bkListTmp;
	}

	public Author getAuthor(final Author anAuthor) {
		final Cursor results = dbCntrlr.selectData(anAuthor.getSelectByLastUpdate());

		Author auth = null;
		if (results.getCount() > 0) {
			results.moveToFirst();
			auth = new Author(results);
		}
		results.close();
		return auth;
	}

	public Author getAuthor(final Long objId) {
		final Cursor results = dbCntrlr.selectData(new Author().getSelectByIdSql(objId));

		Author auth = null;
		if (results.getCount() > 0) {
			results.moveToFirst();
			auth = new Author(results);
		}
		results.close();
		return auth;
	}

	public Author getAuthor(final String authorName) {
		final Cursor results = dbCntrlr.selectData(new Author(authorName).getSelectAuthor());

		Author auth = null;
		if (results.getCount() > 0) {
			results.moveToFirst();
			auth = new Author(results);
		}
		results.close();
		return auth;
	}

	public ArrayList<String> getAuthors() {
		// if (authorsCursor == null) { final Uri getIncdntsUri =
		// Uri.withAppendedPath(InLibrisLibertasData.EBooks.CONTENT_URI, "");
		// final ContentResolver cr = contxt.getContentResolver();
		// authorsCursor = cr.query(getIncdntsUri, null, null, null, null); }
		// return authorsCursor;
		final ArrayList<String> tmpList = new ArrayList<String>();
		final Cursor results = dbCntrlr.selectData(new Author().getSelectAllSql());

		results.moveToFirst();
		for (int x = 0; x < results.getCount(); x++) {
			final Author auth = new Author(results);
			tmpList.add(auth.getAuthorName());
			results.moveToNext();
		}
		results.close();
		return tmpList;
	}

	public ArrayList<Author> getAuthorsForBook(final EBook eBk) {
		final ArrayList<Author> tmpList = new ArrayList<Author>();

		final Cursor results = dbCntrlr
				.selectData(new Author().getSelectAllForBook(eBk.getUniqueIdentifier()));

		results.moveToFirst();
		for (int x = 0; x < results.getCount(); x++) {
			final Author auth = new Author(results);
			tmpList.add(auth);
			results.moveToNext();
		}
		results.close();
		return tmpList;
	}

	public EBook getBook(final EBook tmpBk) {
		final Uri getIncdntsUri = Uri.withAppendedPath(InLibrisLibertasData.EBooks.CONTENT_RECENT_URI, "");
		final ContentResolver cr = contxt.getContentResolver();
		final Cursor results = cr.query(getIncdntsUri, null, null, null, null);

		EBook ebk = null;
		if (results.getCount() > 0) {
			results.moveToFirst();
			ebk = new EBook(results);
			ebk.authors = getAuthorsForBook(ebk);
			ebk.tags = getTagsForBook(ebk);
		}
		results.close();
		return ebk;
	}

	public Integer getBookCountAll() {
		final Uri getIncdntsUri = Uri.withAppendedPath(InLibrisLibertasData.EBooks.CONTENT_RECENT_URI, "");
		final ContentResolver cr = contxt.getContentResolver();
		final Cursor results = cr.query(getIncdntsUri, null, null, null, null);
		return results.getCount();
	}

	public Cursor getBooks() {
		if (ebooksCursor == null) {
			final Uri getIncdntsUri = Uri.withAppendedPath(InLibrisLibertasData.EBooks.CONTENT_URI, "");
			final ContentResolver cr = contxt.getContentResolver();
			ebooksCursor = cr.query(getIncdntsUri, null, null, null, null);
		}
		return ebooksCursor;
	}

	public List<EBook> getBooksForAuthor(final String aStr) {
		// throw new NotImplementedException();
		return null;
	}

	public List<EBook> getBooksInSeries(final String tag) {
		// throw new NotImplementedException();
		return null;
	}

	public List<EBook> getBooksWithTag(final String aStr) {
		// throw new NotImplementedException();
		return null;
	}

	private DbMetadata getDbMetadata() {
		final Cursor results = dbCntrlr.selectData(new DbMetadata().getSelectAllSql());

		if (results.getCount() > 0) {
			results.moveToFirst();
			dbMetaDta = new DbMetadata(results);
		}
		results.close();
		return dbMetaDta;
	}

	public EBook getRecentBook() {
		final Cursor results = dbCntrlr.selectData(new RecentEBook().getSelectAllSql());

		EBook ebk = null;
		results.moveToFirst();
		for (int x = 0; x < results.getCount(); x++) {
			ebk = new EBook(results);
			ebk.authors = getAuthorsForBook(ebk);
			ebk.tags = getTagsForBook(ebk);
			results.moveToNext();
		}
		results.close();
		return ebk;
	}

	public List<EBook> getRecentBooks() {
		final List<EBook> tmpList = new ArrayList<EBook>();
		final Cursor results = dbCntrlr.selectData(new RecentEBook().getSelectAllSql());

		results.moveToFirst();
		for (int x = 0; x < results.getCount(); x++) {
			final EBook ebk = new EBook(results);
			ebk.authors = getAuthorsForBook(ebk);
			ebk.tags = getTagsForBook(ebk);
			tmpList.add(ebk);
			results.moveToNext();
		}
		results.close();
		return tmpList;
	}

	public ArrayList<String> getSeries() {
		final ArrayList<String> tmpList = new ArrayList<String>();
		final Cursor results = dbCntrlr.selectData(new EBook().getSelectAllSeries());

		results.moveToFirst();
		if (results.getCount() > 0) {
			tmpList.add(results.getString(results.getColumnIndex(EBook.FIELD_NAME_series)));
			results.moveToNext();
		}
		results.close();
		return tmpList;
	}

	public Tag getTag(final String tagName) {
		final Cursor results = dbCntrlr.selectData(new Tag().getSelectTag(tagName));

		Tag tg = null;
		if (results.getCount() > 0) {
			results.moveToFirst();
			tg = new Tag();
			tg.setLastUpdated(results.getString(results.getColumnIndex(BaseColumns._ID)));
			tg.setUniqueIdentifier(results.getString(results
					.getColumnIndex(AbstractDataObj.FIELD_NAME_LAST_UPDATED)));
			tg.name = results.getString(results.getColumnIndex(Tag.FIELD_NAME_TAG_NAME));
		}
		results.close();
		return tg;
	}

	public Tag getTag(final Tag tmpBk) {
		final Cursor results = dbCntrlr.selectData(tmpBk.getSelectByLastUpdate());

		Tag tg = null;
		if (results.getCount() > 0) {
			results.moveToFirst();
			tg = new Tag();
			tg.setLastUpdated(results.getString(results.getColumnIndex(BaseColumns._ID)));
			tg.setUniqueIdentifier(results.getString(results
					.getColumnIndex(AbstractDataObj.FIELD_NAME_LAST_UPDATED)));
			tg.name = results.getString(results.getColumnIndex(Tag.FIELD_NAME_TAG_NAME));
		}
		results.close();
		return tg;
	}

	public ArrayList<String> getTags() {
		final ArrayList<String> tmpList = new ArrayList<String>();
		final Cursor results = dbCntrlr.selectData(new Tag().getSelectAllSql());

		results.moveToFirst();
		for (int x = 0; x < results.getCount(); x++) {
			tmpList.add(results.getString(results.getColumnIndex(Tag.FIELD_NAME_TAG_NAME)));
			results.moveToNext();
		}
		results.close();
		return tmpList;
	}

	public ArrayList<Tag> getTagsForBook(final EBook eBk) {
		final ArrayList<Tag> tmpList = new ArrayList<Tag>();

		final Cursor results = dbCntrlr.selectData(new Tag().getSelectAllForBook(eBk.getUniqueIdentifier()));

		results.moveToFirst();
		for (int x = 0; x < results.getCount(); x++) {
			final Tag tg = new Tag();
			tg.setLastUpdated(results.getString(results.getColumnIndex(BaseColumns._ID)));
			tg.setUniqueIdentifier(results.getString(results
					.getColumnIndex(AbstractDataObj.FIELD_NAME_LAST_UPDATED)));
			tg.name = results.getString(results.getColumnIndex(Tag.FIELD_NAME_TAG_NAME));
			tmpList.add(tg);
			results.moveToNext();
		}
		results.close();
		return tmpList;
	}

	public String[] getToken(String str) {
		boolean foundquotes = false;
		final String[] retValues = new String[2];
		String retStr = "";
		try {
			if (str.startsWith("\"")) {
				foundquotes = true;
				// we are looking for the next quote symbol
				final int quoteLoc = str.indexOf('\"', 1);
				retStr = str.substring(1, quoteLoc);
			} else {
				final int spaceLoc = str.indexOf(' ');
				if (spaceLoc < 0) {
					retStr = str;
				} else {
					retStr = str.substring(0, spaceLoc);
				}
			}
		} catch (final Exception e) {
			retStr = str;
		}

		if (foundquotes) {
			str = str.substring(retStr.length() + 2);
		} else {
			str = str.substring(retStr.length());
		}

		retValues[0] = retStr.trim();
		retValues[1] = str.trim();

		return retValues;
	}

	public void importCalibreMetadata(final Handler handler) {
		progressHandler = handler;

		final EBookImporter imprtr = new EBookImporter(this, getDbMetadata());
		imprtr.importBooks("/mnt/sdcard/metadata.calibre");
		imprtr.importBooks("/mnt/extsd/metadata.calibre");
	}

	public void informObserversViaHandler(final ImportProgressObject currProgressObj) {
		if (progressHandler != null) {
			final Message hndlrMsg = progressHandler.obtainMessage();
			hndlrMsg.obj = currProgressObj;
			progressHandler.sendMessage(hndlrMsg);
		}
	}

	public ArrayList<FilterEntry> parseFilter(String filter) {
		final ArrayList<FilterEntry> filterTokns = new ArrayList<FilterEntry>();
		String boolOp = "Or";

		// get first 'token' (whether a word or a quote contained string)
		String[] tkn = getToken(filter.trim());
		filter = tkn[1];
		try {
			if (BooleanOperatorFactory.isBooleanOperator(tkn[0])) {
				boolOp = tkn[0];
				tkn = getToken(filter.trim());
				filter = tkn[1];
			}
		} catch (final StringIndexOutOfBoundsException e) {
			System.out.println("StringIndexOutOfBoundsException parsing filter : " + e.getMessage());
			filter = "";
		} catch (final Exception e) {
			System.out.println("General Exception parsing filter : " + e.getMessage());
			filter = "";
		}

		filterTokns.add(new FilterEntry(BooleanOperatorFactory.getBooleanOperator(boolOp), tkn[0]));

		while (filter.length() > 0) {
			tkn = getToken(filter.trim());
			filter = tkn[1];
			if (BooleanOperatorFactory.isBooleanOperator(tkn[0])) {
				boolOp = tkn[0];
				tkn = getToken(filter.trim());
				filter = tkn[1];
			} else {
				boolOp = "Or";
			}
			filterTokns.add(new FilterEntry(BooleanOperatorFactory.getBooleanOperator(boolOp), tkn[0]));
		}
		return filterTokns;
	}

	public void setFilter(final String filtrStr) {
		filterString = filtrStr;
		setChanged();
		try {
			this.notifyObservers(new EBook());
		} catch (final Exception e) {
			Log.e(LibraryConstants.LOG_TAG + CalibreDataModel.LOG_TAG,
					"finishedImport(): exception notifying observers: " + e.getMessage(), e);
		}
	}

	public void setSortOrder(final SortOrder sOrdr) {
		// FIXME: this.dbMetaDta.currSortOrder = sOrdr;
		updateDbMetaData(dbMetaDta);
	}

	public void updateDbMetaData(final DbMetadata dbMetaData) {
		if (dbMetaData.getUniqueIdentifier() > -1) {
			dbCntrlr.update(dbMetaData.getTableName(), dbMetaData.getContentValues(),
					dbMetaData.getUniqueIdentifier());
		} else {
			dbCntrlr.insert(dbMetaData.getTableName(), null, dbMetaData.getContentValues());
		}
		dbCntrlr.commit();
	}

} // class()

